package cc.rengu.redp.flowable.service.impl;

import cc.rengu.redp.common.exception.RedpException;
import cc.rengu.redp.flowable.domain.*;
import cc.rengu.redp.flowable.service.FlowDesignService;
import cc.rengu.redp.flowable.service.SysWorkFlowModelService;
import cc.rengu.redp.flowable.service.modelValidator.CustomStartNodeValidator;
import cc.rengu.redp.flowable.service.modelValidator.CustomUserNodeValidator;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.flowable.validation.validator.ValidatorSet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SysWorkFlowModelServiceImpl implements SysWorkFlowModelService {

    @Autowired
    private FlowDesignService flowDesignService;

    @Override
    public List<CustomNodeVo> getCustomNodesByModelId(String modelId) throws RedpException {
        List<CustomNodeVo> customNodeVos = new ArrayList<>();

        FlowDesign flowDesign = flowDesignService.getById(modelId);
        if (flowDesign == null) {
            throw new RedpException("无法找到流程配置");
        }
        FlowDesignDrawer flowDesignDrawer = JSON.parseObject(flowDesign.getEditorJson(), FlowDesignDrawer.class);

        //从FlowDesignDrawer提取流程图信息，生成BpmnModel
        BpmnModel bpmnModel = convertFlowDesignToBpmn(flowDesignDrawer);

        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();

        flowElements.forEach(flowElement -> {
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                String jsonStr = userTask.getDocumentation();
                CustomNodeVo customNodeVo = JSONObject.parseObject(jsonStr, CustomNodeVo.class);
                if (customNodeVo == null) {
                    customNodeVo = new CustomNodeVo();
                }
                customNodeVo.setNodeType("1");
                customNodeVo.setNodeId(userTask.getId());
                customNodeVo.setNodeName(userTask.getName());
                customNodeVos.add(customNodeVo);
            }

            if (flowElement instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) flowElement;
                String jsonStr = startEvent.getDocumentation();
                CustomNodeVo customNodeVo = JSONObject.parseObject(jsonStr, CustomNodeVo.class);
                if (customNodeVo == null) {
                    customNodeVo = new CustomNodeVo();
                }
                customNodeVo.setNodeType("0");
                customNodeVo.setNodeId(startEvent.getId());
                customNodeVo.setNodeName(startEvent.getName());
                customNodeVos.add(customNodeVo);
            }
        });

        return customNodeVos;
    }


    @Override
    public String getImageFromModel(String modelId) throws RedpException {
        FlowDesign flowDesign = flowDesignService.getById(modelId);
        if (flowDesign == null || flowDesign.getModelImage().length() == 0) {
            throw new RedpException("无法获取流程图");
        }
        return flowDesign.getModelImage();
    }

    @Override
    public BpmnModel beforeDeploy(String modelId) throws RedpException {
        FlowDesign flowDesign = flowDesignService.getById(modelId);
        if (flowDesign == null) {
            throw new RedpException("无法找到流程配置");
        }
        FlowDesignDrawer flowDesignDrawer = JSON.parseObject(flowDesign.getEditorJson(), FlowDesignDrawer.class);
        //从FlowDesignDrawer提取流程图信息，生成BpmnModel
        BpmnModel bpmnModel = convertFlowDesignToBpmn(flowDesignDrawer);
        //校验BpmnModel
        checkBpmnModel(bpmnModel);
        //对BPMN模型重新调整
        adjustBpmnModel(bpmnModel);
        return bpmnModel;
    }

    /**
     * FlowDesignDrawer 转换为流程引擎中的 BpmnModel
     *
     * @param flowDesignDrawer
     * @return
     */
    private BpmnModel convertFlowDesignToBpmn(FlowDesignDrawer flowDesignDrawer) {
        Map<String, Task> taskList = new HashMap<>();
        BpmnModel model = new BpmnModel();
        Process process = new Process();
        model.addProcess(process);

        process.setId(flowDesignDrawer.getAttr().getId());
        process.setName(flowDesignDrawer.getAttr().getName());

        //CustomProcess 向模型中的流程保存扩展配置信息
        CustomProcess customProcess = flowDesignDrawer.getAttr().getCustomProcess();
        String jsonStr = JSON.toJSONString(customProcess);
        process.setDocumentation(jsonStr);

        //处理流程节点
        flowDesignDrawer.getNodeList().forEach(node -> {
            switch (node.getType()) {
                case FlowDesignDrawer.Node.NODE_TYPE_START: {
                    StartEvent startEvent = new StartEvent();
                    startEvent.setId(node.getId());
                    startEvent.setName(node.getNodeName());
                    process.addFlowElement(startEvent);
                    // CustomUserNode 起始事件扩展信息
                    if (node.getCustomStartNode() != null) {
                        startEvent.setDocumentation(JSON.toJSONString(node.getCustomStartNode()));
                        if (node.getCustomStartNode().getUserIds() != null) {
                            process.setCandidateStarterUsers(node.getCustomStartNode().getUserIds());
                        }
                        if (node.getCustomStartNode().getRoleNames() != null) {
                            process.setCandidateStarterGroups(node.getCustomStartNode().getRoleNames());
                        }
                    }

                    //添加事件监听器
                    setListenerToNode(startEvent, node.getListenerConfigs());
                    break;
                }
                case FlowDesignDrawer.Node.NODE_TYPE_END: {
                    EndEvent endEvent = new EndEvent();
                    endEvent.setId(node.getId());
                    endEvent.setName(node.getNodeName());
                    process.addFlowElement(endEvent);
                    break;
                }
                case FlowDesignDrawer.Node.NODE_TYPE_USER: {
                    UserTask userTask = new UserTask();
                    userTask.setName(node.getNodeName());
                    userTask.setId(node.getId());
                    // CustomUserNode 用户节点扩展信息
                    setCustomNodeToUserTask(userTask, node.getCustomUserNode());
                    process.addFlowElement(userTask);
                    taskList.put(node.getId(), userTask);
                    //添加事件监听器
                    setListenerToNode(userTask, node.getListenerConfigs());
                    break;
                }
                case FlowDesignDrawer.Node.NODE_TYPE_SERVICE: {
                    ServiceTask serviceTask = new ServiceTask();
                    serviceTask.setId(node.getId());
                    serviceTask.setName(node.getNodeName());
                    serviceTask.setImplementation("${" + node.getCustomServiceNode().getDelegateClass() + "}");
                    serviceTask.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
                    serviceTask.setDocumentation(JSON.toJSONString(node.getCustomServiceNode()));
                    process.addFlowElement(serviceTask);
                    taskList.put(node.getId(), serviceTask);
                    break;
                }
                case FlowDesignDrawer.Node.NODE_TYPE_EVENT: {
                    ReceiveTask receiveTask = new ReceiveTask();
                    receiveTask.setId(node.getId());
                    receiveTask.setName(node.getNodeName());
                    process.addFlowElement(receiveTask);
                    taskList.put(node.getId(), receiveTask);
                }
            }
        });


        //处理连接线
        flowDesignDrawer.getLinkList().forEach(link -> {
            SequenceFlow flow = new SequenceFlow();
            flow.setId(link.getId());
            flow.setName(link.getLabel());
            //起始节点
            flow.setSourceRef(link.getSourceId());
            if (taskList.get(link.getSourceId()) != null) {
                List<SequenceFlow> outs = taskList.get(link.getSourceId()).getOutgoingFlows();
                if (outs == null) {
                    outs = new ArrayList<>();
                }
                outs.add(flow);
            }

            // 结束节点
            flow.setTargetRef(link.getTargetId());
            if (taskList.get(link.getTargetId()) != null) {
                List<SequenceFlow> ins = taskList.get(link.getTargetId()).getIncomingFlows();
                if (ins == null) {
                    ins = new ArrayList<>();
                }
                ins.add(flow);
            }

            //添加事件监听器
            setListenerToNode(flow, link.getListenerConfigs());
            //其它自定义配置
            if (link.getCustomSequenceFlow() != null) {
                flow.setDocumentation(JSON.toJSONString(link.getCustomSequenceFlow()));
            }

            process.addFlowElement(flow);
        });

        new BpmnAutoLayout(model).execute();

        return model;
    }

    /**
     * 向节点添加监听器
     * @param flowElement
     * @param listenerConfigs
     */
    private void setListenerToNode(FlowElement flowElement, List<ListenerConfig> listenerConfigs) {
        List<FlowableListener> listeners = new ArrayList<>();
        if (listenerConfigs == null || listenerConfigs.size() == 0) {
            return;
        }
        listenerConfigs.forEach(l -> {
            FlowableListener flowableListener = new FlowableListener();
            flowableListener.setEvent(l.getEvent());
            flowableListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
            flowableListener.setImplementation("${" + l.getName() + "}");
            List<FieldExtension> fieldExtensions = new ArrayList<>();
            l.getFields().forEach(f -> {
                FieldExtension fieldExtension = new FieldExtension();
                fieldExtension.setFieldName(f.getKey());
                fieldExtension.setStringValue(f.getValue());
                fieldExtensions.add(fieldExtension);
            });
            flowableListener.setFieldExtensions(fieldExtensions);
            listeners.add(flowableListener);
        });
        flowElement.setExecutionListeners(listeners);
    }

    /**
     * 向节点保存扩展信息
     *
     * @param userTask
     * @param customUserNode
     */
    private void setCustomNodeToUserTask(UserTask userTask, CustomUserNode customUserNode) {
        String jsonStr = JSON.toJSONString(customUserNode);
        userTask.setDocumentation(jsonStr);
        if (customUserNode.isSetCandidateUserPrevious()) {
            userTask.setAssignee("${assignee}");
            userTask.setCandidateUsers(null);
            userTask.setCandidateGroups(null);
        } else if (customUserNode.isSetAssignToApplicant()) {
            userTask.setAssignee(null);
            userTask.setCandidateGroups(null);
            userTask.setCandidateUsers(null);
        } else {
            userTask.setAssignee(null);
            if (customUserNode.getUserIds() != null) {
                userTask.setCandidateUsers(customUserNode.getUserIds());
            }
            if (customUserNode.getRoleNames() != null) {
                userTask.setCandidateGroups(customUserNode.getRoleNames());
            }
        }
    }

    /**
     * 校验模型
     *
     * @param bpmnModel
     * @throws RedpException
     */
    private void checkBpmnModel(BpmnModel bpmnModel) throws RedpException {
        ProcessValidator validator = new ProcessValidatorFactory().createDefaultProcessValidator();

        //自定义模型校验器
        ValidatorSet validatorSet = new ValidatorSet("Custom flow validators");
        validatorSet.addValidator(new CustomStartNodeValidator());
        validatorSet.addValidator(new CustomUserNodeValidator());
        validator.getValidatorSets().add(validatorSet);

        //开始校验
        List<ValidationError> errors = validator.validate(bpmnModel);
        if (errors.size() > 0) {
            StringBuilder errStrBuilder = new StringBuilder();
            errors.forEach(err -> {
                errStrBuilder.append(err.getDefaultDescription());
                errStrBuilder.append("\n");
            });

            throw new RedpException("模型校验不通过：\n" + errStrBuilder);
        }
    }

    /**
     * 自动编制sequenceFlow、UserTask的id，编制sequenceFlow的转入条件
     *
     * @param bpmnModel
     */
    private void adjustBpmnModel(BpmnModel bpmnModel) {
        Process process = bpmnModel.getProcesses().get(0);
        Collection<FlowElement> flowElements = process.getFlowElements();

        int seqFlowNo = 1;
        int seqNodeNo = 1;

        Map<String, List<GraphicInfo>> tmpFlowGraphic = new HashMap<>();
        Map<String, String> oldFlowToNewFlow = new HashMap<>(); //记录旧ID与新ID的关系，修改delegate自定义参数，key=oldId, value=newId

        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof SequenceFlow) {
                SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                String seqFlowId = String.format("S%02d", seqFlowNo);
                //同步修改图形id
                List<GraphicInfo> flowGraphicList = bpmnModel.getFlowLocationGraphicInfo(sequenceFlow.getId());
                bpmnModel.removeFlowGraphicInfoList(sequenceFlow.getId());

                //设置新id
                oldFlowToNewFlow.put(sequenceFlow.getId(), seqFlowId);
                tmpFlowGraphic.put(seqFlowId, flowGraphicList);
                sequenceFlow.setId(seqFlowId);
                seqFlowNo++;
            }
        }

        //通过缓存更新，避免同步删除和添加时，因id重复导致可能数据丢失的问题
        tmpFlowGraphic.entrySet().stream().forEach(entry -> bpmnModel.addFlowGraphicInfoList(entry.getKey(), entry.getValue()));

        Map<String, GraphicInfo> tmpGraphic = new HashMap<>();

        //对上部分有依赖，故分开两个循环
        for (FlowElement flowElement : flowElements) {
            if (flowElement instanceof StartEvent) {
                // nothing to do
            } else if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;

                String seqNodeId = String.format("ND%02d", seqNodeNo);
                //同步修改图形id
                GraphicInfo nodeGraphic = bpmnModel.getGraphicInfo(userTask.getId());
                bpmnModel.removeGraphicInfo(userTask.getId());
                //设置新id
                tmpGraphic.put(seqNodeId, nodeGraphic);
                userTask.setId(seqNodeId);
                List<SequenceFlow> outgoingFlows = userTask.getOutgoingFlows();
                //存在选择的分支才设置条件表达式
                if (outgoingFlows.size() >= 2) {
                    for (SequenceFlow sequenceFlow : outgoingFlows) {
                        sequenceFlow.setConditionExpression("${flowStatus=='" + sequenceFlow.getId() + "'}");
                    }
                }

                //同步其他节点，这些用到UserTask的id
                userTask.getIncomingFlows().forEach(sequenceFlow -> sequenceFlow.setTargetRef(seqNodeId));
                outgoingFlows.forEach(sequenceFlow -> sequenceFlow.setSourceRef(seqNodeId));
                seqNodeNo++;
            } else if (flowElement instanceof ServiceTask) {
                ServiceTask serviceTask = (ServiceTask) flowElement;

                String seqNodeId = String.format("SVR%02d", seqNodeNo);
                //同步修改图形id
                GraphicInfo nodeGraphic = bpmnModel.getGraphicInfo(serviceTask.getId());
                bpmnModel.removeGraphicInfo(serviceTask.getId());
                //设置新id
                tmpGraphic.put(seqNodeId, nodeGraphic);
                serviceTask.setId(seqNodeId);
                List<SequenceFlow> outgoingFlows = serviceTask.getOutgoingFlows();
                //存在选择的分支才设置条件表达式
                if (outgoingFlows.size() >= 2) {
                    for (SequenceFlow sequenceFlow : outgoingFlows) {
                        sequenceFlow.setConditionExpression("${flowStatus=='" + sequenceFlow.getId() + "'}");
                    }
                }

                //同步其他节点，这些用到UserTask的id
                serviceTask.getIncomingFlows().forEach(sequenceFlow -> sequenceFlow.setTargetRef(seqNodeId));
                outgoingFlows.forEach(sequenceFlow -> sequenceFlow.setSourceRef(seqNodeId));

                //修改自定义参数的流出分支参数，更换新id
                CustomServiceNode customServiceNode = JSON.parseObject(serviceTask.getDocumentation(), CustomServiceNode.class);
                if (StringUtils.isNotBlank(customServiceNode.getPerformSuccessOutgoingId())) {
                    customServiceNode.setPerformSuccessOutgoingId(oldFlowToNewFlow.get(customServiceNode.getPerformSuccessOutgoingId()));
                }
                if (StringUtils.isNotBlank(customServiceNode.getPerformFailOutgoingId())) {
                    customServiceNode.setPerformFailOutgoingId(oldFlowToNewFlow.get(customServiceNode.getPerformFailOutgoingId()));
                }
                serviceTask.setDocumentation(JSON.toJSONString(customServiceNode));

                seqNodeNo++;
            } else if (flowElement instanceof EndEvent) {
                EndEvent endEvent = (EndEvent) flowElement;
                List<FlowableListener> listeners = new ArrayList<>();
                FlowableListener listener = new FlowableListener();
                listener.setEvent(ExecutionListener.EVENTNAME_START);
                listener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_DELEGATEEXPRESSION);
                listener.setImplementation("${endEventForBuildBizModelDelegate}");
                listeners.add(listener);
                endEvent.setExecutionListeners(listeners);
            } else if (flowElement instanceof SequenceFlow) {
                SequenceFlow sequenceFlow = (SequenceFlow) flowElement;
                CustomSequenceFlow customSequenceFlow = JSON.parseObject(sequenceFlow.getDocumentation(), CustomSequenceFlow.class);
                //转换自定义配置中流程线的ID编码
                if (customSequenceFlow != null) {
                    List<CustomSequenceFlow.IncomingDecideOutgoings> newOutgoings = customSequenceFlow.getIncomingDecideOutgoings().stream()
                            .map(i -> {
                                CustomSequenceFlow.IncomingDecideOutgoings k = customSequenceFlow.new IncomingDecideOutgoings();
                                k.setKey(i.getKey());
                                k.setSequenceFlowId(oldFlowToNewFlow.get(i.getSequenceFlowId()));
                                return k;
                            }).collect(Collectors.toList());
                    customSequenceFlow.setIncomingDecideOutgoings(newOutgoings);
                    sequenceFlow.setDocumentation(JSON.toJSONString(customSequenceFlow));
                }
            }
        }

        tmpGraphic.entrySet().stream().forEach(entry -> bpmnModel.addGraphicInfo(entry.getKey(), entry.getValue()));
    }
}
